﻿using OpenNETCF.IoC;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;

namespace IoTCM
{
    internal partial class RequestProcessor
    {
        private SessionService m_sessionService;
        private Dictionary<string, IHost> m_hosts;
        private IThingProvider m_provider;

        public bool SessionsEnabled { get; set; }

        public RequestProcessor(IThingProvider provider, bool sessionsEnabled)
        {
            SessionsEnabled = sessionsEnabled;
            m_provider = provider;
            m_sessionService = new SessionService();

            GetHosts(provider);
        }

        public string[] SupportedFormats
        {
            get { return m_hosts.Values.Select(c => c.Format).ToArray(); }
        }

        private void GetHosts(IThingProvider provider)
        {
            m_hosts = new Dictionary<string, IHost>(StringComparer.InvariantCultureIgnoreCase);

            // find all hosts
            var hosts = RootWorkItem.Items.Get<IHost>();

            if (hosts.Count() == 0)
            {
                // no loaded hosts, we can't do anything
                throw new Exception("No hosts available");
            }

            foreach (var host in hosts)
            {
                if (m_hosts.ContainsKey(host.Format))
                {
                    throw new Exception("Host already registered for format: " + host.Format);
                }

                host.SetThingProvider(provider);
                m_hosts.Add(host.Format, host);
            }
        }

        public void ProcessRequest(HttpListenerContext context)
        {
            switch (context.Request.HttpMethod)
            {
                case "GET":
                    ProcessGet(context);
                    break;
                case "POST":
                    ProcessPost(context);
                    break;
                case "PUT":
                    ProcessPut(context);
                    break;
                case "DELETE":
                    ProcessDelete(context);
                    break;
                default:
                    break;
            }
        }

        private string GetRequestParameter(HttpListenerContext context, string parameterName)
        {
            // see if there is a requested format override (support a hearder or querystring override)
            var f = context.Request.Headers[parameterName];
            if (!string.IsNullOrEmpty(f))
            {
                return f;
            }
            else
            {
                f = context.Request.QueryString[parameterName];
                if (!string.IsNullOrEmpty(f))
                {
                    return f;
                }
            }

            return null;
        }

        private string GetRequestedFormat(HttpListenerContext context)
        {
            var format = GetRequestParameter(context, "format") ?? "xml";

            return format.ToLower();
        }
        private bool ValidateSession(HttpListenerContext context, IHost host)
        {
            Session session;
            return ValidateSession(context, host, out session);
        }

        private bool ValidateSession(HttpListenerContext context, IHost host, out Session session)
        {
            session = null;

            // should we even check the session?  If not, pass
            if (!SessionsEnabled) return true;

            var token = GetRequestParameter(context, "session") ?? string.Empty;

            // was a token passed in?  If not, fail
            if (string.IsNullOrEmpty(token))
            {
                SendErrorResponse(context, HttpStatusCode.Forbidden, null, "Session Token is required");
                return false;
            }

            if (!m_sessionService.ValidateSession(token, out session))
            {
                SendErrorResponse(context, HttpStatusCode.Forbidden, null, "Session is not valid");
                return false;
            }

            return true;
        }

        private IHost GetRequestedHost(HttpListenerContext context)
        {
            var format = GetRequestedFormat(context);
            if (!m_hosts.ContainsKey(format))
            {
                SendErrorResponse(context, HttpStatusCode.InternalServerError, null, "No host for format: " + format);
                return null;
            }

            return m_hosts[format];
        }

        private void SendErrorResponse(HttpListenerContext context, HttpStatusCode status, IHost host, string message)
        {
            var contentType = "text";

            if (host != null)
            {
                message = host.GetSerializedNameValuePair("error", message);
                contentType = host.MimeType;
            }

            SendResponse(context, message, contentType, status, "Error");
        }

        private void SendNotAuthorizedError(HttpListenerContext context, IHost host)
        {
            SendErrorResponse(context, HttpStatusCode.Forbidden, host, "You do not have Write authority.");
        }

        private void SendNotFoundError(HttpListenerContext context, IHost host)
        {
            SendErrorResponse(context, HttpStatusCode.NotFound, host, "Not Found");
        }

        private void SendInvalidCredentialsError(HttpListenerContext context, IHost host)
        {
            SendErrorResponse(context, HttpStatusCode.Forbidden, host, "Not Authorized");
        }

        private void SendNotAllowedError(HttpListenerContext context, IHost host)
        {
            SendErrorResponse(context, HttpStatusCode.MethodNotAllowed, host, "Request not allowed");
        }

        private void SendAcceptedResponse(HttpListenerContext context, IHost host, string message)
        {
            SendResponse(context, host.GetSerializedNameValuePair("message", message), host.MimeType, HttpStatusCode.Accepted, "OK");
        }

        private void SendExceptionReturn(HttpListenerContext context, IHost host, Exception ex)
        {
            SendErrorResponse(context, HttpStatusCode.InternalServerError, host, ex.Message);
        }

        private void SendResponse(HttpListenerContext context, string responseData)
        {
            SendResponse(context, responseData, "text");
        }

        private void SendResponse(HttpListenerContext context, string responseData, string contentType)
        {
            SendResponse(context, responseData, contentType, HttpStatusCode.OK, "OK");
        }

        private void SendResponse(HttpListenerContext context, string responseData, string contentType, HttpStatusCode status, string statusDescription)
        {
            var response = Encoding.UTF8.GetBytes(responseData);

            // the response is valid
            context.Response.StatusCode = (int)status;
            context.Response.StatusDescription = statusDescription;

            if (!string.IsNullOrEmpty(contentType))
            {
                context.Response.ContentType = contentType;
            }

            context.Response.OutputStream.Write(response, 0, response.Length);
            context.Response.OutputStream.Flush();
            context.Response.OutputStream.Close();
        }

        private string GetRequestData(HttpListenerContext context)
        {
            return new StreamReader(context.Request.InputStream, context.Request.ContentEncoding).ReadToEnd();
        }
    }
}
